package com.hengpeng.itfinoss.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.core.codec.Base64;
import org.springframework.security.core.codec.Hex;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

public class DigestAuthUtils
{

	private static final String[] EMPTY_STRING_ARRAY = new String[0];

	public static HttpHeaders createAuthHeaders(String uri, String httpMethod, String userName, String realName,
			String password, String digestKey)
	{
		HttpHeaders requestHeaders = new HttpHeaders();
		// Authorization
		requestHeaders.set("Authorization", DigestAuthUtils.createDigestAuthorization(uri, userName, realName,
				password, digestKey, httpMethod));
		// String userPassword = CLIENT + ":" + MSBS_CLIENT_PASSWORD_$;
		// String encoding = new String(Base64.encodeBase64(userPassword.getBytes()), Charset.forName("UTF-8"));
		// requestHeaders.set("Authorization", "Basic " + encoding);

		// Accept
		List<MediaType> mediaTypes = new ArrayList<MediaType>();
		mediaTypes.add(MediaType.APPLICATION_XML);
		mediaTypes.add(MediaType.APPLICATION_JSON);
		requestHeaders.setAccept(mediaTypes);

		requestHeaders.set("Accept-Charset", "UTF-8");
		return requestHeaders;
	}

	public static String createDigestAuthorization(String uri, String userName, String realName, String password,
			String digestKey, String httpMethod) throws IllegalArgumentException
	{
		int nonceValiditySeconds = 100;
		long expiryTime = System.currentTimeMillis() + (nonceValiditySeconds * 1000);
		String signatureValue = new String(DigestUtils.md5Hex(expiryTime + ":" + digestKey));
		String nonceValue = expiryTime + ":" + signatureValue;
		String nonceValueBase64 = new String(Base64.encode(nonceValue.getBytes()));
		String generateDigest = DigestAuthUtils.generateDigest(false, userName, realName, password, httpMethod, uri,
				null, nonceValueBase64, null, null);
		String authenticateHeader = "Digest username=\"" + userName + "\", realm=\"" + realName + "\", response=\""
				+ generateDigest + "\", uri=\"" + uri + "\", " + "nonce=\"" + nonceValueBase64 + "\"";
		return authenticateHeader;
	}

	public static String encodePasswordInA1Format(String username, String realm, String password)
	{
		String a1 = username + ":" + realm + ":" + password;
		String a1Md5 = md5Hex(a1);

		return a1Md5;
	}

	/**
	 * Computes the <code>response</code> portion of a Digest authentication header. Both the server and user agent
	 * should compute the <code>response</code> independently. Provided as a static method to simplify the coding of
	 * user agents.
	 * @param passwordAlreadyEncoded true if the password argument is already encoded in the correct format. False if it
	 *        is plain text.
	 * @param username the user's login name.
	 * @param realm the name of the realm.
	 * @param password the user's password in plaintext or ready-encoded.
	 * @param httpMethod the HTTP request method (GET, POST etc.)
	 * @param uri the request URI.
	 * @param qop the qop directive, or null if not set.
	 * @param nonce the nonce supplied by the server
	 * @param nc the "nonce-count" as defined in RFC 2617.
	 * @param cnonce opaque string supplied by the client when qop is set.
	 * @return the MD5 of the digest authentication response, encoded in hex
	 * @throws IllegalArgumentException if the supplied qop value is unsupported.
	 */
	public static String generateDigest(boolean passwordAlreadyEncoded, String username, String realm, String password,
			String httpMethod, String uri, String qop, String nonce, String nc, String cnonce)
			throws IllegalArgumentException
	{
		String a1Md5 = null;
		String a2 = httpMethod + ":" + uri;
		String a2Md5 = md5Hex(a2);

		if (passwordAlreadyEncoded)
		{
			a1Md5 = password;
		}
		else
		{
			a1Md5 = DigestAuthUtils.encodePasswordInA1Format(username, realm, password);
		}

		String digest;

		if (qop == null)
		{
			// as per RFC 2069 compliant clients (also reaffirmed by RFC 2617)
			digest = a1Md5 + ":" + nonce + ":" + a2Md5;
		}
		else if ("auth".equals(qop))
		{
			// As per RFC 2617 compliant clients
			digest = a1Md5 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + a2Md5;
		}
		else
		{
			throw new IllegalArgumentException("This method does not support a qop: '" + qop + "'");
		}

		String digestMd5 = new String(md5Hex(digest));

		return digestMd5;
	}

	public static String md5Hex(String data)
	{
		MessageDigest digest;
		try
		{
			digest = MessageDigest.getInstance("MD5");
		}
		catch (NoSuchAlgorithmException e)
		{
			throw new IllegalStateException("No MD5 algorithm available!");
		}

		return new String(Hex.encode(digest.digest(data.getBytes())));
	}

	/**
	 * Splits a <code>String</code> at the first instance of the delimiter.
	 * <p>
	 * Does not include the delimiter in the response.
	 * </p>
	 * @param toSplit the string to split
	 * @param delimiter to split the string up with
	 * @return a two element array with index 0 being before the delimiter, and index 1 being after the delimiter
	 *         (neither element includes the delimiter)
	 * @throws IllegalArgumentException if an argument was invalid
	 */
	public static String[] split(String toSplit, String delimiter)
	{
		Assert.hasLength(toSplit, "Cannot split a null or empty string");
		Assert.hasLength(delimiter, "Cannot use a null or empty delimiter to split a string");

		if (delimiter.length() != 1)
		{
			throw new IllegalArgumentException("Delimiter can only be one character in length");
		}

		int offset = toSplit.indexOf(delimiter);

		if (offset < 0)
		{
			return null;
		}

		String beforeDelimiter = toSplit.substring(0, offset);
		String afterDelimiter = toSplit.substring(offset + 1);

		return new String[] { beforeDelimiter, afterDelimiter };
	}

	/**
	 * Takes an array of <code>String</code>s, and for each element removes any instances of
	 * <code>removeCharacter</code>, and splits the element based on the <code>delimiter</code>. A <code>Map</code> is
	 * then generated, with the left of the delimiter providing the key, and the right of the delimiter providing the
	 * value.
	 * <p>
	 * Will trim both the key and value before adding to the <code>Map</code>.
	 * </p>
	 * @param array the array to process
	 * @param delimiter to split each element using (typically the equals symbol)
	 * @param removeCharacters one or more characters to remove from each element prior to attempting the split
	 *        operation (typically the quotation mark symbol) or <code>null</code> if no removal should occur
	 * @return a <code>Map</code> representing the array contents, or <code>null</code> if the array to process was null
	 *         or empty
	 */
	public static Map<String, String> splitEachArrayElementAndCreateMap(String[] array, String delimiter,
			String removeCharacters)
	{
		if ((array == null) || (array.length == 0))
		{
			return null;
		}

		Map<String, String> map = new HashMap<String, String>();

		for (int i = 0; i < array.length; i++)
		{
			String postRemove;

			if (removeCharacters == null)
			{
				postRemove = array[i];
			}
			else
			{
				postRemove = StringUtils.replace(array[i], removeCharacters, "");
			}

			String[] splitThisArrayElement = split(postRemove, delimiter);

			if (splitThisArrayElement == null)
			{
				continue;
			}

			map.put(splitThisArrayElement[0].trim(), splitThisArrayElement[1].trim());
		}

		return map;
	}

	public static String[] splitIgnoringQuotes(String str, char separatorChar)
	{
		if (str == null)
		{
			return null;
		}

		int len = str.length();

		if (len == 0)
		{
			return EMPTY_STRING_ARRAY;
		}

		List<String> list = new ArrayList<String>();
		int i = 0;
		int start = 0;
		boolean match = false;

		while (i < len)
		{
			if (str.charAt(i) == '"')
			{
				i++;
				while (i < len)
				{
					if (str.charAt(i) == '"')
					{
						i++;
						break;
					}
					i++;
				}
				match = true;
				continue;
			}
			if (str.charAt(i) == separatorChar)
			{
				if (match)
				{
					list.add(str.substring(start, i));
					match = false;
				}
				start = ++i;
				continue;
			}
			match = true;
			i++;
		}
		if (match)
		{
			list.add(str.substring(start, i));
		}

		return list.toArray(new String[list.size()]);
	}
}
